﻿from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from videolog import insert_or_update_viewlog, ViewLog, write_log
from videos import (
    Video,VideoRequest,get_db_connection,init_database,VideoDatabase
)
from recommendation import (
    load_recommendation_data, get_user_recommendations, get_hot_videos, 
    data_loader, logger
)
from contextlib import asynccontextmanager
from typing import List, Optional, Dict, Any
import uvicorn
from fastapi import Request, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from datetime import datetime

# 初始化数据库
init_database()

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时加载推荐数据
    logger.info("开始加载推荐数据...")
    if load_recommendation_data():
        logger.info("推荐数据加载完成，服务准备就绪")
    else:
        logger.error("推荐数据加载失败，推荐功能可能无法正常工作")
    yield
    logger.info("服务关闭")

app = FastAPI(
    title="视频推荐API服务", 
    description="视频管理及推荐系统",
    lifespan=lifespan
)

@app.get("/")
def root():
    return {"status": "ok", "message": "FastAPI 服务运行中"}

# ================= 视频操作接口 =================
class VideoRequest(BaseModel):
    op: str  # add / modify / delete
    video: Video

@app.post("/video")
def video_api(req: VideoRequest):
    """视频信息接口 - 支持添加、修改和删除操作"""
    vid = req.video.video_id

    # 验证操作类型
    if req.op not in ["add", "modify", "delete"]:
        return {"code": 400, "result": [], "msg": "非法操作类型"}

    try:
        if req.op == "add":
            # 添加视频 - 使用数据库类
            if VideoDatabase.check_video_exists(vid):
                return {"code": 400, "result": [], "msg": "视频ID已存在"}
                       
            # 获取视频数据
            video_data = req.video.model_dump()
            path = video_data.get('path', '')
            
            # 调用数据库类的添加方法
            VideoDatabase.add_video(video_data, path)
            return {"code": 200, "result": [vid], "msg": "success"}

        elif req.op == "modify":
            # 修改视频 - 使用数据库类
            if not VideoDatabase.check_video_exists(vid):
                return {"code": 404, "result": [], "msg": "视频ID不存在"}
              
            # 获取视频数据
            video_data = req.video.model_dump()
            path = video_data.get('path', '')
            
            # 调用数据库类的更新方法
            VideoDatabase.update_video(vid, video_data, path)
            return {"code": 200, "result": [vid], "msg": "success"}

        elif req.op == "delete":
            # 软删除视频 - 使用数据库类
            if not VideoDatabase.check_video_exists(vid):
                return {"code": 404, "result": [], "msg": "视频ID不存在"}
            
            # 调用数据库类的删除方法
            VideoDatabase.delete_video(vid)
            return {"code": 200, "result": [vid], "msg": "success"}
    except Exception as e:
        # 异常处理
        return {"code": 500, "result": [], "msg": f"失败原因: {str(e)}"}

@app.get("/video/{video_id}")
def get_video(video_id: str):
    """获取视频信息接口"""
    try:
        # 调用数据库类获取视频信息
        video_info = VideoDatabase.get_video(video_id)
        
        if not video_info:
            return {"code": 404, "result": [], "msg": "视频不存在或已被删除"}
            
        return {"code": 200, "result": [video_info], "msg": "success"}
    except Exception as e:
        # 异常处理
        return {"code": 500, "result": [], "msg": f"失败原因: {str(e)}"}

# ================= 视频历史接口 =================
@app.post("/videolog")
def add_video_log(log: ViewLog):
    try:
        write_log(log)
        success, msg = insert_or_update_viewlog(log)

        if not success:
            # 插入/更新失败
            return {
                "code": 500,
                "result": [],
                "msg": msg
            }

        # 成功
        return {
            "code": 200,
            "result": [],
            "msg": msg
        }

    except Exception as e:
        # 异常情况
        return {
            "code": 500,
            "result": [],
            "msg": f"异常错误: {str(e)}"
        }

# ================= 推荐接口 =================
class RecommendationRequest(BaseModel):
    user_id: str
    video_count: Optional[int] = 20

class SimpleRecommendationResponse(BaseModel):
    code: int
    result: List[str]
    msg: str

@app.get("/recommend")
async def root_recommend():
    return {"message": "视频推荐API服务（支持个性化推荐/热门视频推荐）", "status": "running"}

@app.post("/recommend", response_model=SimpleRecommendationResponse)
async def recommend_videos(request: RecommendationRequest):
    """
    单用户推荐接口
    """
    try:
        # 处理 video_count 参数范围限制
        if request.video_count < 12:
            video_count = 12
        elif request.video_count > 100:
            video_count = 100
        else:
            video_count = request.video_count
        
        result = get_user_recommendations(
            user_id=request.user_id,
            top_n=video_count,  # 使用处理后的 video_count
            temperature=1.5,
            gender_boost_factor=1.5
        )
        
        video_ids = []
        if not result["selected_videos"].empty:
            video_ids = [str(row["video_id"]) for _, row in result["selected_videos"].iterrows()]
        
        return SimpleRecommendationResponse(
            code=200,
            result=video_ids,
            msg=f"success (已排除{result.get('watched_count', 0)}个已观看视频)"
        )
    except Exception as e:
        logger.error(f"为用户 {request.user_id} 生成推荐时出错: {str(e)}")
        return SimpleRecommendationResponse(
            code=500,
            result=[],
            msg=f"推荐服务异常: {str(e)}"
        )

@app.get("/recommend/all")
async def recommend_all_users(video_count: int = 20, gender_boost_factor: float = 1.5):
    """批量为所有用户生成推荐"""
    try:
        # 同样处理 video_count 参数范围限制
        if video_count < 12:
            top_n = 12
        elif video_count > 100:
            top_n = 100
        else:
            top_n = video_count
            
        if data_loader.user_df is None or data_loader.user_id_field not in data_loader.user_df.columns:
            return {
                "code": 500,
                "result": {},
                "msg": "用户画像数据未正确加载，无法进行批量推荐"
            }
            
        user_ids = data_loader.user_df[data_loader.user_id_field].unique()
        results = {}
        processed_count = 0
        
        for user_id in user_ids:
            try:
                user_result = get_user_recommendations(
                    user_id=user_id, 
                    top_n=top_n,  # 使用处理后的 top_n
                    temperature=1.5,
                    gender_boost_factor=gender_boost_factor
                )
                
                video_ids = []
                if not user_result["selected_videos"].empty:
                    video_ids = [str(row["video_id"]) for _, row in user_result["selected_videos"].iterrows()]
                
                results[str(user_id)] = {
                    "code": 200,
                    "result": video_ids,
                    "msg": f"success (已排除{user_result.get('watched_count', 0)}个已观看视频)"
                }
                processed_count += 1
            except Exception as e:
                logger.error(f"处理用户 {user_id} 时出错: {str(e)}，已跳过")
                results[str(user_id)] = {
                    "code": 500,
                    "result": [],
                    "msg": f"处理用户时出错: {str(e)}"
                }
                continue
        
        return {
            "processed_users": processed_count,
            "results": results,
            "timestamp": datetime.now().isoformat()
        }
    except Exception as e:
        logger.error(f"批量推荐时出错: {str(e)}")
        return {
            "code": 500,
            "result": {},
            "msg": f"批量推荐服务异常: {str(e)}"
        }

@app.exception_handler(status.HTTP_405_METHOD_NOT_ALLOWED)
async def method_not_allowed_handler(request: Request, exc):
    """自定义405方法不允许的响应格式"""
    return JSONResponse(
        status_code=500,  # 按需求返回500状态码
        content={
            "code": 500,
            "result": [],
            "msg": "Method NOT Allowed"
        }
    )

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    """自定义请求验证异常响应格式"""
    # 检查是否是请求体缺失的错误
    for error in exc.errors():
        if error["type"] == "missing" and error["loc"] == ["body"]:
            return JSONResponse(
                status_code=500,  # 建议用400表示请求错误，而非500服务器错误
                content={
                    "code": 500,
                    "result": [],
                    "msg": "input is null"
                }
            )
    
    # 其他验证错误保持统一格式
    return JSONResponse(
        status_code=500,
        content={
            "code": 500,
            "result": [],
            "msg": "请求参数错误"
        }
    )

# ================= 主程序入口 =================
if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True)